<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D UV贴图编辑器</title>
    <style>
        /* 保持原有样式不变 */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        html,
        body {
            width: 100%;
            height: 100%;
            overflow: hidden;
        }

        .texture-panel {
            position: fixed;
            left: 20px;
            top: 20px;
            width: 160px;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 10px;
            padding: 15px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            z-index: 100;
        }

        .texture-item {
            margin-bottom: 12px;
            cursor: pointer;
            transition: all 0.2s;
            border: 2px solid transparent;
            border-radius: 6px;
            overflow: hidden;
        }

        .texture-item:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
        }

        .texture-item.active {
            border-color: #2196F3;
        }

        .texture-preview {
            width: 100%;
            height: auto;
            display: block;
        }

        .texture-name {
            text-align: center;
            font-family: Arial, sans-serif;
            font-size: 12px;
            color: #666;
            padding: 4px 0;
        }
    </style>
</head>

<body>
    <div id="app"></div>
    <div class="texture-panel" id="texturePanel"></div>

    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
        import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

        let infoSign = null;
        let isHovering = false;
        const baseScale = 0.3;
        const maxScale = 0.6;

        // 初始化场景
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0xeeeeee);

        // 相机设置
        const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 2, 5);

        // 渲染器
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.outputEncoding = THREE.sRGBEncoding; // 启用色彩空间转换
        renderer.toneMapping = THREE.ACESFilmicToneMapping; // 使用电影级色调映射
        renderer.toneMappingExposure = 1.2; // 提升整体曝光
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;
        document.body.appendChild(renderer.domElement);

        // 控制器
        const controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;

        // 灯光
        const ambientLight = new THREE.AmbientLight(0xffffff, 1.2);
        scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
        directionalLight.position.set(5, 8, 6);
        directionalLight.castShadow = true;
        scene.add(directionalLight);

        // 模型和贴图状态
        let currentModel = null;
        let currentTexture = null;
        let currentTextureInfo = null

        // 贴图配置
        const uvTextures = [
            {
                name: "鳞片纹理",
                type: "uv",
                url: "https://threejs.org/examples/textures/uv_grid_opengl.jpg"
            },
            {
                name: "迷彩图案",
                type: "uv",
                url: "https://threejs.org/examples/textures/colors.png"
            },
            {
                name: "火焰视频",
                type: "sp",
                url: "http://localhost:5173/public/1747141201035_video.mp4"
            }

        ];

        // 初始化贴图面板
        function initTexturePanel() {
            const panel = document.getElementById('texturePanel');
            uvTextures.forEach(texture => {
                const item = document.createElement('div');
                item.className = 'texture-item';

                let previewHTML = texture.type === 'sp'
                    ? `<video src="${texture.url}" class="texture-preview" muted loop preload="metadata"></video>`
                    : `<img src="${texture.url}" class="texture-preview">`;

                item.innerHTML = `
                ${previewHTML}
                <div class="texture-name">${texture.name}</div>
            `;

                item.addEventListener('click', async () => {
                    document.querySelectorAll('.texture-item').forEach(i => i.classList.remove('active'));
                    item.classList.add('active');
                    currentTextureInfo = texture;

                    let textureLoaded = null;
                    if (texture.type === 'uv') {
                        textureLoaded = await new THREE.TextureLoader().loadAsync(texture.url);
                        textureLoaded.flipY = false;
                    } else if (texture.type === 'sp') {
                        const video = document.createElement('video');
                        video.src = texture.url;
                        video.crossOrigin = "anonymous";
                        video.loop = true;
                        video.muted = true;
                        video.playsInline = true;
                        await video.play();
                        textureLoaded = new THREE.VideoTexture(video);
                        textureLoaded.minFilter = THREE.LinearFilter;
                        textureLoaded.magFilter = THREE.LinearFilter;
                    }

                    currentTexture = textureLoaded;
                    if (currentModel && currentTexture) {
                        currentModel.traverse(child => {
                            if (child.isMesh) {
                                child.material.map = currentTexture;
                                child.material.needsUpdate = true;
                            }
                        });
                    }
                    document.body.style.cursor = 'default'; // 移除十字光标提示
                });

                panel.appendChild(item);
            });
        }

        // 创建文本纹理函数
        function createTextTexture(text) {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');

            // 根据文字内容设置画布尺寸
            const fontSize = 48;
            ctx.font = `${fontSize}px Arial`;
            const textWidth = ctx.measureText(text).width;

            canvas.width = textWidth + 40;
            canvas.height = fontSize + 40;

            // 重新设置字体并绘制
            ctx.fillStyle = '#fff';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.font = `${fontSize}px Arial`;
            ctx.fillStyle = '#333';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(text, canvas.width / 2, canvas.height / 2);

            const texture = new THREE.CanvasTexture(canvas);
            texture.needsUpdate = true;
            return texture;
        }

        // 创建指示牌
        function createInfoSign(text) {
            const texture = createTextTexture(text);
            const material = new THREE.MeshBasicMaterial({
                map: texture,
                transparent: true,
                side: THREE.DoubleSide
            });
            const geometry = new THREE.PlaneGeometry(1, 0.5);
            const mesh = new THREE.Mesh(geometry, material);

            mesh.renderOrder = 999; // 确保最后渲染
            mesh.scale.set(baseScale, baseScale, baseScale);
            return mesh;
        }

        // 创建线条辅助函数
        function createLine(start, end) {
            const geometry = new THREE.BufferGeometry().setFromPoints([start, end]);
            const material = new THREE.LineDashedMaterial({
                color: 0xff0000,    // 红色虚线
                dashSize: 0.2,
                gapSize: 0.1,
            });
            const line = new THREE.Line(geometry, material);
            line.computeLineDistances(); // 必须调用才能显示虚线
            return line;
        }

        // 加载模型
        new GLTFLoader().load(
            'https://cdn.jsdelivr.net/gh/KhronosGroup/glTF-Sample-Models/2.0/Duck/glTF-Binary/Duck.glb',
            gltf => {
                currentModel = gltf.scene;

                // 居中模型
                infoSign = createInfoSign('鸭子');
                const box = new THREE.Box3().setFromObject(currentModel);
                const center = box.getCenter(new THREE.Vector3());
                currentModel.position.sub(center);
                box.setFromObject(currentModel);

                // 定位到模型顶部
                const topPosition = new THREE.Vector3(
                    0,
                    box.max.y + 0.5, // 在模型顶部上方0.5单位
                    0
                );
                infoSign.position.copy(topPosition);
                scene.add(infoSign);
                // 创建指示线（从指示牌底部到模型顶部）
                const baseScale = 1; // 确保与指示牌的缩放一致
                const lineStart = infoSign.position.clone();
                lineStart.y -= 0.25 * baseScale; // 向下移动指示牌高度的一半

                const lineEnd = new THREE.Vector3(0, box.max.y, 0); // 指向模型顶部中心

                const indicatorLine = createLine(lineStart, lineEnd);
                indicatorLine.renderOrder = 999; // 确保在最后渲染
                scene.add(indicatorLine);



                scene.add(currentModel);
            }
        );

        // 点击应用贴图
        document.addEventListener('click', () => {
            if (!currentModel || !currentTexture) return;

            console.log('currentTexture', currentTexture)

            // 更新所有模型的材质贴图
            currentModel.traverse(child => {
                if (child.isMesh) {
                    child.material.map = currentTexture;
                    child.material.needsUpdate = true;
                }
            });
        });

        // 初始化
        initTexturePanel();

        // 动画循环
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }
        animate();

        // 窗口调整
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
    </script>
</body>

</html>